using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Proto;
using Proto.Cluster;

namespace HostedService;

public class ProtoHost : IHostedService
{
    private readonly IHostApplicationLifetime _appLifetime;
    private readonly Cluster _cluster;
    private readonly ILogger<ProtoHost> _logger;

    public ProtoHost(
        Cluster cluster,
        ILogger<ProtoHost> logger,
        IHostApplicationLifetime appLifetime
    )
    {
        _cluster = cluster;
        _logger = logger;
        _appLifetime = appLifetime;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Starting cluster...");
        _appLifetime.ApplicationStarted.Register(
            () => SafeTask.Run(RunRequestLoop, _appLifetime.ApplicationStopping)
        );
        _appLifetime.ApplicationStopping.Register(OnStopping);
        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;

    //flood the system, to see how it reacts upon shutdown.
    private async Task RunRequestLoop()
    {
        await Task.Yield();

        var rnd = Random.Shared;

        while (!_appLifetime.ApplicationStopping.IsCancellationRequested)
        {
            var tasks = new List<Task>();

            for (var i = 0; i < 1000; i++)
            {
                var id = rnd.Next(0, 100000);
                var t = _cluster.RequestAsync<int>(
                    $"abc{id}",
                    "kind",
                    123,
                    new CancellationTokenSource(20000).Token
                );
                tasks.Add(t);
            }

            await Task.WhenAll(tasks);
        }
    }

    private void OnStopping()
    {
        var completedInTime = true;
        try
        {
            _cluster
                .ShutdownAsync()
                .WaitAsync(TimeSpan.FromSeconds(15))
                .GetAwaiter()
                .GetResult();
        }
        catch (TimeoutException)
        {
            completedInTime = false;
        }

        if (!completedInTime)
            _logger.LogError("Shut down cluster timed out...");
    }
}
